package com.neusoft.databus.sap.ssh;

import java.io.IOException;
import java.net.Socket;

import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.neusoft.databus.agent.interactive.collect.SerialModbusCollector;
import com.neusoft.databus.agent.interactive.collect.Socket2ModbusCollector;
import com.neusoft.databus.agent.interactive.collect.SocketModbusCollector;
import com.neusoft.databus.agent.parser.modbus.ModBusASCIIParser;
import com.neusoft.databus.agent.parser.modbus.ModBusRTUParser;
import com.neusoft.databus.agent.parser.modbus.ModBusTCPParser;
import com.neusoft.databus.common.connection.SerialConnection;
import com.neusoft.databus.common.connection.SocketConnection;
import com.neusoft.databus.common.parser.Parser;
import com.neusoft.databus.common.utils.DigitalUtils;

import gnu.io.SerialPort;
import lombok.extern.slf4j.Slf4j;
import sshd.shell.springboot.autoconfiguration.SshdShellCommand;

@Slf4j
@Component
@SshdShellCommand(value = "modbus", description = "modbus test. Type 'modbus' for supported subcommands")
public class ModbusCommand {

    @SshdShellCommand(value = "socket2", description = "modbus with parallel socket. Usage: modbus socket2 rtu|tcp IP:PORT slave command")
    public String socket2(String arg) throws IOException {
        if (StringUtils.isEmpty(arg)) {
            return "illegal command";
        }
        final String[] arguments = arg.split("\\s+");
        if (arguments.length != 4) {
            return "illegal command";
        }

        final String protocol = arguments[0];
        final String[] address = arguments[1].split(":");
        final int slave = Integer.valueOf(arguments[2]);
        final String command = arguments[3];

        return this.bysocket("socket2", protocol, address, slave, command);

    }

    @SshdShellCommand(value = "socket", description = "modbus with serial socket. Usage: modbus socket rtu|tcp IP:PORT slave command")
    public String socket(String arg) throws IOException {
        if (StringUtils.isEmpty(arg)) {
            return "illegal command";
        }
        final String[] arguments = arg.split("\\s+");
        if (arguments.length != 4) {
            return "illegal command";
        }

        final String protocol = arguments[0];
        final String[] address = arguments[1].split(":");
        final int slave = Integer.valueOf(arguments[2]);
        final String command = arguments[3];

        return this.bysocket("socket", protocol, address, slave, command);

    }

    @SshdShellCommand(value = "serial", description = "modbus with physical serial. Usage: modbus serial rtu|ascii serial_port_name slave command")
    public String serial(String arg) throws IOException {
        if (StringUtils.isEmpty(arg)) {
            return "illegal command";
        }
        final String[] arguments = arg.split("\\s+");
        if (arguments.length != 4) {
            return "illegal command";
        }

        final String protocol = arguments[0];
        final String comName = arguments[1];
        final int slave = Integer.valueOf(arguments[2]);
        final String command = arguments[3].toUpperCase();

        Parser parser = null;
        switch (protocol) {
        case "rtu":
            parser = new ModBusRTUParser();
            break;
        case "ascii":
            parser = new ModBusASCIIParser();
            break;
        default:
            parser = new ModBusRTUParser();
        }

        final SerialModbusCollector collector = new SerialModbusCollector(parser);

        final SerialConnection connection = new SerialConnection();
        connection.setComValue(comName);
        connection.setParity(0);
        connection.setDataBits(8);
        connection.setStopBits(1);
        connection.setBaudrate(9600);
        connection.setRetry(0);
        connection.setSotimeout(3);
        SerialPort serialPort = null;
        try {
            serialPort = collector.getConnector().getConn(connection);
            final byte[] reqBytes = "rtu".equals(protocol)
                    ? ((ModBusRTUParser) parser).generateReq(String.format("%02x", slave), command)
                    : ((ModBusASCIIParser) parser).generateReq(String.format("%02x", slave), command);

            final String req = DigitalUtils.bytesToHexString(reqBytes);

            final byte[] result =
                    collector.retrieve(req, serialPort.getInputStream(), serialPort.getOutputStream(), 1000, 1);
            if (null == result) {
                return "failed to response";
            } else {
                return DigitalUtils.bytesToHexString(result);
            }
        } catch (final Exception e) {
            ModbusCommand.log.error(e.getMessage());
            collector.getConnector().invalidateConn(connection, serialPort);
            serialPort = null;
            return e.getMessage();
        } finally {
            if (null != serialPort) {
                collector.getConnector().returnConn(connection, serialPort);
            }
        }

    }

    private String bysocket(String pool, String protocol, String[] address, int slave, String command) {
        Parser parser = null;
        switch (protocol) {
        case "rtu":
            parser = new ModBusRTUParser();
            break;
        case "tcp":
            parser = new ModBusTCPParser();
            break;
        default:
            parser = new ModBusTCPParser();
        }

        final SocketModbusCollector collector =
                "socket".equals(pool) ? new SocketModbusCollector(parser) : new Socket2ModbusCollector(parser);

        final SocketConnection connection = new SocketConnection();
        connection.setIpAddress(address[0]);
        connection.setPort(Integer.valueOf(address[1]));

        Socket socket = null;
        try {
            socket = collector.getConnector().getConn(connection);
            final byte[] reqBytes = "rtu".equals(protocol)
                    ? ((ModBusRTUParser) parser).generateReq(String.format("%02x", slave), command)
                    : ((ModBusTCPParser) parser).generateReq(String.format("%02x", slave), command);

            final String req = DigitalUtils.bytesToHexString(reqBytes);
            final byte[] result =
                    collector.retrieve(req, socket.getInputStream(), socket.getOutputStream());
            if (null == result) {
                return "failed to response";
            } else {
                return DigitalUtils.bytesToHexString(result);
            }
        } catch (final Exception e) {
            ModbusCommand.log.error(e.getMessage());
            collector.getConnector().invalidateConn(connection, socket);
            socket = null;
            return e.getMessage();
        } finally {
            if (null != socket) {
                collector.getConnector().returnConn(connection, socket);
            }
        }
    }

}
